/**
 * 
 */
package qy.qyalgotrader.mystrategy;

import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.map.ListOrderedMap;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.google.common.math.DoubleMath;

import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.barfeed.BaseBarFeed;
import qy.jalgotrade.broker.Order;
import qy.jalgotrade.dataseries.BarDataSeries;
import qy.jalgotrade.dataseries.DataSeries;
import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.strategy.position.Position;
import qy.jalgotrade.talibext.Indicator;
import qy.jalgotrade.technical.ATR;
import qy.jalgotrade.technical.BollingerBands;
import qy.jalgotrade.technical.CrossUils;
import qy.jalgotrade.technical.MACD;
import qy.jalgotrade.utils.CommonUtils;
import qy.jalgotrade.utils.resamplebase.ResampleUtils;
import qy.qyalgotrader.mystrategy.StratUtils.MySignalMode;
import qy.qyalgotrader.technical.CDTS;
import qy.qyalgotrader.technical.IntraDayMvModel;
import qy.qyalgotrader.utils.CommonDataUtils;

/**
 * <pre>
 * 多周期趋势跟踪, 支持:
 * 1. 过滤器;
 * 2. 跟踪止盈止损
 * </pre>
 * 
 * @author c-geo
 *
 */
public class TrendV2 extends MultiFreqsTraderBase {

	// @formatter:off
	public static final Map<String, Object> DEFAULT_OPT_PARAMS = MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
		// 风险管理:
		{"riskMan", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"hardSl", 0.005},
			{"cashUtil", 0.55},
			{"volaEstim", 0.03}
		})},
		// 多周期参数:
		{"freqsMan", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"MICRO", "MINUTE"},
			{"SHORT", "MINUTE_3"},
			{"MEDIUM", "MINUTE_5"},
			{"LONG", "MINUTE_15"},
			{"TREND_RECOG", "MINUTE_30"}
		})},
		{"bBandsPeriod", 20}, {"numStdDev", 2.0}, // BOLL
		{"ctBollPeriod", 2400}, {"ctBollStd", 2.0}, // CDTS BOLL
		{"ctaPeriod", 10}, {"ctaBollStd", 1.0}, // CTA BOLL
		{"atrPeriod", 26}, // ATR
		{"fastEma", 12}, {"slowEma", 26}, {"signalEma", 9}, {"diffSignal", false}, // MACD
		{"useMaxDdSl", true}, {"maxDdAtr", 2.0}, {"maxDdSlAbs", false}, // 止盈 / 止损参数
		{"signalMode", MySignalMode.DEFAULT}, {"loggerOff", false}, // 其他参数
		// 回测模拟 Broker 参数:
		{"cashOrBrokerParams", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"initialCash", 1000000.0},
			{"commission", 0.0,},
			{"allowNegativeCash", true},
			{"fillStrategy.noVolumeLimit", true}
		})},
	});

	/**
	 * 信号模式: 多 / 空 / 多空
	 */
	private MySignalMode __signalMode;

	// BOLL:
	private int __bBandsPeriod; private double __numStdDev;

	// CT(Convergence & Divergence (VWAP) of Time-Sharing Diagram) BOLL:
	private int __ctBollPeriod; private double __ctBollStd;

	// CTA:
	private int __ctaPeriod; private double __ctaBollStd;

	// ATR:
	private int __atrPeriod;

	// MACD:
	private int __fastEma; private int __slowEma; private int __signalEma;
	@SuppressWarnings("unused")
	private boolean __diffSignal;

	// 止盈 / 止损参数:
	private boolean __useMaxDdSl; // 是否使用 最大回撤止盈 / 止损
	private double __maxDdAtr;
	private boolean __maxDdSlAbs; // 是否在头寸产生亏损时执行
	// @formatter:on

	// 其他参数:
	private boolean __loggerOff;

	/**
	 * @param barFeed
	 * @param instruments
	 * @throws Exception
	 */
	public TrendV2(BaseBarFeed barFeed, List<String> instruments) throws Exception {

		this(barFeed, instruments, DEFAULT_OPT_PARAMS);
	}

	/**
	 * @param barFeed
	 * @param instruments
	 * @param optParams
	 * @throws Exception
	 */
	public TrendV2(BaseBarFeed barFeed, List<String> instruments, Map<String, Object> optParams) throws Exception {

		super(barFeed, instruments, optParams);

		__signalMode = (MySignalMode) optParams.getOrDefault("singalMode", DEFAULT_OPT_PARAMS.get("singalMode")); // 信号模式: 多 / 空 / 多空

		// 指标参数:
		// BOLL:
		__bBandsPeriod = (int) optParams.getOrDefault("bBandsPeriod", DEFAULT_OPT_PARAMS.get("bBandsPeriod"));
		__numStdDev = (double) optParams.getOrDefault("numStdDev", DEFAULT_OPT_PARAMS.get("numStdDev"));
		// CT BOLL:
		__ctBollPeriod = (int) optParams.getOrDefault("ctBollPeriod", DEFAULT_OPT_PARAMS.get("ctBollPeriod"));
		__ctBollStd = (double) optParams.getOrDefault("ctBollStd", DEFAULT_OPT_PARAMS.get("ctBollStd"));
		// CTA:
		__ctaPeriod = (int) optParams.getOrDefault("ctaPeriod", DEFAULT_OPT_PARAMS.get("ctaPeriod"));
		__ctaBollStd = (double) optParams.getOrDefault("ctaBollStd", DEFAULT_OPT_PARAMS.get("ctaBollStd"));
		// ATR:
		__atrPeriod = (int) optParams.getOrDefault("atrPeriod", DEFAULT_OPT_PARAMS.get("atrPeriod"));
		// MACD:
		__fastEma = (int) optParams.getOrDefault("fastEma", DEFAULT_OPT_PARAMS.get("fastEma"));
		__slowEma = (int) optParams.getOrDefault("slowEma", DEFAULT_OPT_PARAMS.get("slowEma"));
		__signalEma = (int) optParams.getOrDefault("signalEma", DEFAULT_OPT_PARAMS.get("signalEma"));
		__diffSignal = (boolean) optParams.getOrDefault("diffSignal", DEFAULT_OPT_PARAMS.get("diffSignal"));
		// 是否使用 最大回撤止盈 / 止损:
		__useMaxDdSl = (boolean) optParams.getOrDefault("useMaxDdSl", DEFAULT_OPT_PARAMS.get("useMaxDdSl"));
		__maxDdAtr = (double) optParams.getOrDefault("maxDdAtr", DEFAULT_OPT_PARAMS.get("maxDdAtr"));
		__maxDdSlAbs = (boolean) optParams.getOrDefault("maxDdSlAbs", DEFAULT_OPT_PARAMS.get("maxDdSlAbs"));
		// @formatter:off
		// __mfTechIndDicts (多周期技术指标: {Frequency: techIdnDict {techIdnName: {instrument: techIndcs}}}):
		Table<Bar.Frequency, String, Map<String, Object>> mfTechIndDicts = getMfTechIndDicts();
		for (Bar.Frequency e : getFeedMfDict().keySet()) {
			BaseBarFeed feed = getFeedMfDict().get(e);
			for (String instrument : getInstruments()) {
				Map<String, Object> tdInfos = CommonDataUtils.getTradeInfos(instrument); // 包含交易时间等参数
				LocalTime baseTm = (LocalTime) tdInfos.get("openTimeAm");
				// BOLL:
				mfTechIndDicts.put(e, "bbandsDict", MapUtils.putAll(new HashMap<>(), new Object[]
						{instrument, new BollingerBands(
								(SequenceDataSeries<Double>) ((BarDataSeries) feed.__getitem__(instrument)).getCloseDataSeries(),
								__bBandsPeriod, __numStdDev)}
				));
				if (e == Bar.Frequency.MINUTE) {
					// CT(Convergence & Divergence (VWAP) of Time-Sharing Diagram) BOLL:
					mfTechIndDicts.put(e, "ctDict", MapUtils.putAll(new HashMap<>(), new Object[]
							{instrument, new CDTS((BarDataSeries) feed.__getitem__(instrument))}
					));
					// CT-BOll:
					mfTechIndDicts.put(e, "ctBollDict", MapUtils.putAll(new HashMap<>(), new Object[]
							{instrument, new BollingerBands(
									(CDTS) mfTechIndDicts.get(e, "ctDict").get(instrument), __ctBollPeriod, __ctBollStd)}
					));
					// CTA:
					mfTechIndDicts.put(e, "ctaDict", MapUtils.putAll(new HashMap<>(), new Object[]
							{instrument, new IntraDayMvModel(
									(CDTS) mfTechIndDicts.get(e, "ctDict").get(instrument), __ctaPeriod, baseTm,
									Optional.empty(),
									OptionalDouble.of(__ctaBollStd),
									OptionalDouble.empty(),
									OptionalDouble.empty(), OptionalInt.empty())}
					));
				}
				// ATR:
				mfTechIndDicts.put(e, "atrDict", MapUtils.putAll(new HashMap<>(), new Object[]
						{instrument, new ATR((BarDataSeries) feed.__getitem__(instrument), __atrPeriod)}
				));
				// MACD:
				mfTechIndDicts.put(e, "macdDict", MapUtils.putAll(new HashMap<>(), new Object[]
						{instrument, new MACD((SequenceDataSeries<Double>) ((BarDataSeries) feed.__getitem__(instrument)).getCloseDataSeries(),
				                __fastEma, __slowEma, __signalEma)}
				));
			}
		}
		// @formatter:on
		// 其他参数:
		__loggerOff = (boolean) optParams.getOrDefault("loggerOff", DEFAULT_OPT_PARAMS.get("loggerOff"));
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onEnterOk(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onEnterOk(Position position) {

		if (!__loggerOff) {
			if (getLogger().isWarnEnabled()) {
				warning(String.format("entered %s position: {instrument: %s, shares: %s, avgEnterPrice: %.2f}",
				        position.getShares() > 0 ? "LONG" : "SHORT", position.getInstrument(), position.getShares(),
				        position.getEntryOrder().getAvgFillPrice()));
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.qyalgotrader.mystrategy.MultiFreqsTraderBase#onEnterCanceled(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onEnterCanceled(Position position) {

		super.onEnterCanceled(position);
	}

	/*
	 * (non-Javadoc)
	 * @see qy.qyalgotrader.mystrategy.MultiFreqsTraderBase#onExitOk(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onExitOk(Position position) {

		super.onExitOk(position);

		if (!__loggerOff) {
			if (getLogger().isWarnEnabled()) {
				warning(String.format("exit position: {instrument: %s, shares: %s, avgExitPrice: %.2f}",
				        position.getInstrument(), position.getExitOrder().getQuantity(),
				        position.getExitOrder().getAvgFillPrice()));
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onExitCanceled(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onExitCanceled(Position position) {

		// If the exit was canceled, re-submit it.
		try {
			position.exitMarket();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onStart()
	 */
	@Override
	public void onStart() {

		info("TrendV1 running started!");
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onFinish(qy.jalgotrade.bar.Bars)
	 */
	@Override
	public void onFinish(Bars bars) {

		info("TrendV1 running finished!");
	}

	/* (non-Javadoc)
	 * @see qy.qyalgotrader.mystrategy.MultiFreqsTraderBase#onSignalBars(qy.jalgotrade.bar.Bars)
	 */
	@Override
	protected void onSignalBars(Bars bars) {

		try {
			__doOnBars(bars);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/* (non-Javadoc)
	 * @see qy.qyalgotrader.mystrategy.MultiFreqsTraderBase#onResampledBars(qy.jalgotrade.bar.Bars)
	 */
	@Override
	protected void onResampledBars(Bars bars) {

		try {
			__doOnBars(bars);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @param bars
	 * @throws Exception
	 */
	private void __doOnBars(Bars bars) throws Exception {

		if (bars.getBarsFrequency() == getSignalFreq()) {
			Map<String, Boolean> exitLongSigDict = __exitLongSignal(bars);
			for (String e : bars.getInstruments()) {
				if (exitLongSigDict.containsKey(e) && exitLongSigDict.get(e)) {
					Position longPos = getActivePositionFor(e, Order.Action.BUY);
					if (longPos != null && !longPos.exitActive()) {
						longPos.exitMarket();
					}
				}
			}

			Map<String, Boolean> exitShortSigDict = __exitShortSignal(bars);
			for (String e : bars.getInstruments()) {
				if (exitShortSigDict.containsKey(e) && exitShortSigDict.get(e)) {
					Position shortPos = getActivePositionFor(e, Order.Action.SELL_SHORT);
					if (shortPos != null && !shortPos.exitActive()) {
						shortPos.exitMarket();
					}
				}
			}

			// 检查多 / 空模式:
			if (__signalMode != MySignalMode.SHORT_ONLY) {
				Table<String, String, Object> enterLongSigDict = __enterLongSignal(bars);
				for (String e : bars.getInstruments()) {
					if ((boolean) enterLongSigDict.get(e, "enter")
					        && getActivePositionFor(e, Order.Action.BUY) == null) {
						double qty = CommonDataUtils.getCnFuTradeUnit(e); // 每手合约交易单位 (RMB)
						enterLong(e, qty);
					}
				}
			}

			if (__signalMode != MySignalMode.LONG_ONLY) {
				Table<String, String, Object> enterShortSigDict = __enterShortSignal(bars);
				for (String e : bars.getInstruments()) {
					if ((boolean) enterShortSigDict.get(e, "enter")
					        && getActivePositionFor(e, Order.Action.SELL_SHORT) == null) {
						double qty = CommonDataUtils.getCnFuTradeUnit(e); // 每手合约交易单位 (RMB)
						enterShort(e, qty);
					}
				}
			}
		}
	}

	/**
	 * 
	 * @param bars
	 * @return
	 */
	private Table<String, String, Object> __enterLongSignal(Bars bars) {

		Table<String, String, Object> signalDict = HashBasedTable.create();
		for (String e : bars.getInstruments()) {
			signalDict.put(e, "enter", false);
			signalDict.put(e, "freq", getSignalFreq());
		}

		for (String e : bars.getInstruments()) {
			Map<String, Map<String, Object>> signalTiDicts = getMfTechIndDicts().row(bars.getBarsFrequency());
			// 检查各技术指标序列均可用: BOLL, ATR, MACD:
			if (Double.isNaN(((BollingerBands) signalTiDicts.get("bbandsDict").get(e)).getUpperBand().__getitem__(-1))
			        || Double.isNaN(((ATR) signalTiDicts.get("atrDict").get(e)).__getitem__(-1))
			        || Double.isNaN(((MACD) signalTiDicts.get("macdDict").get(e)).__getitem__(-1))) {
				continue;
			}

			// 策略主逻辑: DIF 上穿 0 轴, 开多:
			if (CrossUils.crossAbove((MACD) signalTiDicts.get("macdDict").get(e), StratUtils.CONST_DS_0) > 0) {
				// 产生多头信号:
				signalDict.put(e, "enter", true);
				signalDict.put(e, "freq", getSignalFreq());

				if (!__loggerOff) {
					if (getLogger().isWarnEnabled()) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[TrendV1]%s: <%s> enter LONG signal!", CommonDataUtils.toWindCode(e),
						        getSignalFreq()));
					}
				}
			}
		}

		return signalDict;
	}

	/**
	 * 
	 * @param bars
	 * @return
	 * @throws Exception
	 */
	private Map<String, Boolean> __exitLongSignal(Bars bars) throws Exception {

		Map<String, Boolean> exitSignalDict = new HashMap<>();
		for (String e : bars.getInstruments()) {
			exitSignalDict.put(e, false);
		}

		// signal 周期:
		BaseBarFeed signalBarFeed = getFeedMfDict().get(getSignalFreq());
		for (String e : bars.getInstruments()) {
			Map<String, Map<String, Object>> signalTiDicts = getMfTechIndDicts().row(bars.getBarsFrequency());
			Position longPos = getActivePositionFor(e, Order.Action.BUY);
			if (longPos != null && !longPos.exitActive()) {
				// 最高点回撤止损:
				if (__useMaxDdSl) {
					double atr = ((ATR) signalTiDicts.get("atrDict").get(e)).__getitem__(-1);
					DataSeries<Double> closeDs = ((BarDataSeries) signalBarFeed.__getitem__(e)).getCloseDataSeries();
					ZonedDateTime orderSubmitDm;
					if (getSignalFreq() != getFeed().getFrequency()) {
						orderSubmitDm = ResampleUtils.getBeginDateTime(longPos.getEntryOrder().getSubmitDateTime(),
						        getSignalFreq());
					} else {
						orderSubmitDm = longPos.getEntryOrder().getSubmitDateTime();
					}
					int ct = closeDs.__len__() - closeDs.getAbsoluteByDateTime(orderSubmitDm);
					double drawDown;
					if (ct > 1) {
						drawDown = Indicator.MAX(closeDs, ct, ct).getRight() - bars.__getitem__(e).getClose();
					} else {
						drawDown = 0;
					}

					if (DoubleMath.fuzzyCompare(Math.abs(drawDown), __maxDdAtr * atr,
					        CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH) > 0) {
						if (__maxDdSlAbs) {
							// 多头头寸未产生亏损, 则不执行:
							if (longPos.getPnL() < 0) {
								exitSignalDict.put(e, true);
							}
						} else {
							exitSignalDict.put(e, true);
						}

						if (exitSignalDict.get(e)) {
							if (!__loggerOff) {
								if (getLogger().isWarnEnabled()) {
									// TODO: wddatautils.get_asset_name(e)
									warning(String.format(
									        "[TrendV1]%s: <%s> exit LONG signal (Big-Drawdown, [%.2f ATR])!",
									        CommonDataUtils.toWindCode(e), getSignalFreq(), Math.abs(drawDown) / atr));
								}
							}
							continue;
						}
					}
				}

				// 策略平仓主逻辑: DIF 下穿 0 轴:
				if (CrossUils.crossBelow(((MACD) signalTiDicts.get("macdDict").get(e)), StratUtils.CONST_DS_0) > 0) {

					exitSignalDict.put(e, true);

					if (!__loggerOff) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[TrendV1]%s: <%s> exit LONG signal (DIF-Fall-0)!",
						        CommonDataUtils.toWindCode(e), getSignalFreq()));
					}
				}
			}
		}

		return exitSignalDict;
	}

	/**
	 * 
	 * @param bars
	 * @return
	 */
	private Table<String, String, Object> __enterShortSignal(Bars bars) {

		Table<String, String, Object> signalDict = HashBasedTable.create();
		for (String e : bars.getInstruments()) {
			signalDict.put(e, "enter", false);
			signalDict.put(e, "freq", getSignalFreq());
		}

		for (String e : bars.getInstruments()) {
			Map<String, Map<String, Object>> signalTiDicts = getMfTechIndDicts().row(bars.getBarsFrequency());
			// 检查各技术指标序列均可用: BOLL, ATR, MACD:
			if (Double.isNaN(((BollingerBands) signalTiDicts.get("bbandsDict").get(e)).getUpperBand().__getitem__(-1))
			        || Double.isNaN(((ATR) signalTiDicts.get("atrDict").get(e)).__getitem__(-1))
			        || Double.isNaN(((MACD) signalTiDicts.get("macdDict").get(e)).__getitem__(-1))) {
				continue;
			}

			// 策略主逻辑: DIF 下穿 0 轴, 开空:
			if (CrossUils.crossBelow((MACD) signalTiDicts.get("macdDict").get(e), StratUtils.CONST_DS_0) > 0) {
				// 产生空头信号:
				signalDict.put(e, "enter", true);
				signalDict.put(e, "freq", getSignalFreq());

				if (!__loggerOff) {
					if (getLogger().isWarnEnabled()) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[TrendV1]%s: <%s> enter SHORT signal!", CommonDataUtils.toWindCode(e),
						        getSignalFreq()));
					}
				}
			}
		}

		return signalDict;
	}

	/**
	 * 
	 * @param bars
	 * @return
	 * @throws Exception
	 */
	private Map<String, Boolean> __exitShortSignal(Bars bars) throws Exception {

		Map<String, Boolean> exitSignalDict = new HashMap<>();
		for (String e : bars.getInstruments()) {
			exitSignalDict.put(e, false);
		}

		// signal 周期:
		BaseBarFeed signalBarFeed = getFeedMfDict().get(getSignalFreq());
		for (String e : bars.getInstruments()) {
			Map<String, Map<String, Object>> signalTiDicts = getMfTechIndDicts().row(bars.getBarsFrequency());
			Position shortPos = getActivePositionFor(e, Order.Action.SELL_SHORT);
			if (shortPos != null && !shortPos.exitActive()) {
				// 最低点反弹止损:
				if (__useMaxDdSl) {
					double atr = ((ATR) signalTiDicts.get("atrDict").get(e)).__getitem__(-1);
					DataSeries<Double> closeDs = ((BarDataSeries) signalBarFeed.__getitem__(e)).getCloseDataSeries();
					ZonedDateTime orderSubmitDm;
					if (getSignalFreq() != getFeed().getFrequency()) {
						orderSubmitDm = ResampleUtils.getBeginDateTime(shortPos.getEntryOrder().getSubmitDateTime(),
						        getSignalFreq());
					} else {
						orderSubmitDm = shortPos.getEntryOrder().getSubmitDateTime();
					}
					int ct = closeDs.__len__() - closeDs.getAbsoluteByDateTime(orderSubmitDm);
					double drawDown;
					if (ct > 1) {
						drawDown = Indicator.MIN(closeDs, ct, ct).getRight() - bars.__getitem__(e).getClose();
					} else {
						drawDown = 0;
					}

					if (DoubleMath.fuzzyCompare(Math.abs(drawDown), __maxDdAtr * atr,
					        CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH) > 0) {
						if (__maxDdSlAbs) {
							// 空头头寸未产生亏损, 则不执行:
							if (shortPos.getPnL() < 0) {
								exitSignalDict.put(e, true);
							}
						} else {
							exitSignalDict.put(e, true);
						}

						if (exitSignalDict.get(e)) {
							if (!__loggerOff) {
								if (getLogger().isWarnEnabled()) {
									// TODO: wddatautils.get_asset_name(e)
									warning(String.format(
									        "[TrendV1]%s: <%s> exit SHORT signal (Big-Rebound, [%.2f ATR])!",
									        CommonDataUtils.toWindCode(e), getSignalFreq(), Math.abs(drawDown) / atr));
								}
							}
							continue;
						}
					}
				}

				// 策略平仓主逻辑: DIF 上穿 0 轴:
				if (CrossUils.crossAbove(((MACD) signalTiDicts.get("macdDict").get(e)), StratUtils.CONST_DS_0) > 0) {

					exitSignalDict.put(e, true);

					if (!__loggerOff) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[TrendV1]%s: <%s> exit SHORT signal (DIF-Break-0)!",
						        CommonDataUtils.toWindCode(e), getSignalFreq()));
					}
				}
			}
		}

		return exitSignalDict;
	}
}
